import copy
import inspect
import types
from inspect import Parameter
import allure
import pytest
from hotloads.debugtalk import DebugTalk
from utils.func_creation_handler import create_function_from_parameters
from utils.log_handler import logger
from utils.render_template_handler import rend_template_any
from utils.request_handler import RequestHandler


class YamlRunner(object):
    def __init__(self, case_list, module):
        self.case_list = case_list
        self.module = module

    def run(self):
        if not isinstance(self.case_list, list):
            self.case_list = [self.case_list]
        case = {}
        for case_dict in self.case_list:
            case_name = case_dict.get("name")
            case[case_name] = [case_dict]
            config_params = []
            config_fixtures = case_dict.get("fixtures", [])
            config_fixtures, config_params = self.parameters_date(config_fixtures, config_params)
            if case_dict.get("parameters"):
                fixture_params = case_dict.get("parameters")
                fixture = fixture_params[0]
                params = fixture_params[1:]
                params = rend_template_any(params)
                config_fixtures.extend(fixture)
                config_params.extend(params)

            def execute_yaml_case(args):
                call_function_name = inspect.getframeinfo(inspect.currentframe().f_back)[2]
                for step in case[call_function_name]:
                    # 获取被调用函数名称
                    case_info = {}
                    rend_data = {}
                    rend_data.update({"request": step["request"]})
                    rend_data.update({"title": step["title"]})
                    if step.get("extract", None):
                        rend_data.update({"extract": step["extract"]})
                    if step.get("validate", None):
                        rend_data.update({"validate": step["validate"]})
                    if step.get("skip", None):
                        rend_data.update({"skip": step["skip"]})
                    elif step.get("skipif", None):
                        rend_data.update({"skipif": step["skipif"]})
                    copy_value = copy.deepcopy(rend_data)  # 深拷贝一份新的value

                    after_rend_data = self.request_pre(args, copy_value)
                    case_info.update(after_rend_data)
                    allure.dynamic.feature(step["feature"])
                    allure.dynamic.story(step["story"])
                    allure.dynamic.title(case_info["title"])
                    allure.dynamic.description(f"请求的url: {after_rend_data['request']['url']}</b>")
                    if after_rend_data.get("skip"):
                        reason = "暂时不需要测试"
                        pytest.skip(reason)
                    if after_rend_data.get("skipif"):
                        expression = eval(after_rend_data.get("skipif"))
                        if expression:
                            reason = after_rend_data.get("skipif") + "-->暂时不需要测试"
                            pytest.skip(reason)
                    logger.info(f"{step['feature']}-{step['story']}用例执行")
                    res = RequestHandler(DebugTalk()).get_request_data(case_info)
            f = create_function_from_parameters(
                func=execute_yaml_case,
                # parameters 传内置fixture 和 用例fixture
                parameters=self.function_parameters(config_fixtures),
                documentation=case_name,
                func_name=case_name,
                func_filename=f"{self.module.__name__}.py",
            )
            # 向 module 中加入函数
            case_short_name = case_name.split("_")[1]
            setattr(self.module, str(case_name), f)
            if config_params:
                # 向 module 中加参数化数据的属性
                setattr(self.module, case_short_name, config_params)

    @staticmethod
    def parameters_date(fixtures, parameters):
        """
            参数化实现2种方式：
        方式1：
            config:
               name: post示例
               fixtures: username, password
               parameters:
                 - [test1, '123456']
                 - [test2, '123456']
        方式2：
            config:
               name: post示例
               parameters:
                 - {"username": "test1", "password": "123456"}
                 - {"username": "test2", "password": "1234562"}
        :returns
        fixtures: 用例需要用到的fixtures:  ['username', 'password']
        parameters: 参数化的数据list of list : [['test1', '123456'], ['test2', '123456']]
        """
        if isinstance(fixtures, str):
            # 字符串切成list
            fixtures = [item.strip(" ") for item in fixtures.split(',')]
        if isinstance(parameters, list) and len(parameters) >= 1:
            if isinstance(parameters[0], dict):
                # list of dict
                params = list(parameters[0].keys())
                new_parameters = []
                for item in parameters:
                    new_parameters.append(list(item.values()))
                # fixtures 追加参数化的参数
                for param in params:
                    if param not in fixtures:
                        fixtures.append(param)
                return fixtures, new_parameters
            else:
                # list of list
                return fixtures, parameters
        else:
            return fixtures, []

    @staticmethod
    def function_parameters(config_fixtures) -> list:
        """ 测试函数传 fixture """
        # 测试函数的默认请求参数
        _function_parameters = []
        # 获取传给用例的 fixtures
        if isinstance(config_fixtures, str):
            config_fixtures = [item.strip(" ") for item in config_fixtures.split(',')]
        for fixture in config_fixtures:
            _function_parameters.append(
                Parameter(fixture, Parameter.POSITIONAL_OR_KEYWORD),
            )
        return _function_parameters

    @staticmethod
    def request_pre(args, copy_value):
        """
        将yaml文件中request键对应的${{xx}}替换掉对应参数化的值
        :param args: 参数化之后的变量值
        :param copy_value: 测试用例中request键对于的值
        :return: 返回替换之后的request键对于的值
        """
        context = {}
        context.update(args)
        request_value = rend_template_any(copy_value, **context)
        return request_value
